﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Yxinmt.SmarterStreaming
{
    public delegate void PbSetEventCallBack(UInt32 event_id, Int64 param1, Int64 param2, UInt64 param3, UInt64 param4, [MarshalAs(UnmanagedType.LPStr)] String param5, [MarshalAs(UnmanagedType.LPStr)] String param6, IntPtr param7);

    public class SmartPublisher
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern IntPtr GetForegroundWindow();

        SmartPublisherConfig _Config { get; set; }

        System.Windows.Window _Window { get; set; }

        /// <summary>
        /// 窗体句柄
        /// </summary>
       IntPtr _WindowHandle { get; set; }

        public SmartPublisher(System.Windows.Window window, SmartPublisherConfig config)
        {
            _Window = window;
            _Config = config;

            AuidoInputDevicesList = new List<string>();
            _StatusMessageList = new ConcurrentQueue<string>();

            InitLoad();
        }

        IntPtr _PublisherHandle { get; set; }

        bool _IsPublishing { get; set; }       //是否在推流状态
        bool _IsRecording { get; set; }       //是否在录像状态

        static NT_PB_SDKEventCallBack _EventCallBack;

        static ConcurrentDictionary<IntPtr, PbSetEventCallBack> _SetEventCallBackDictionary { get; set; }

        static SmartPublisher()
        {
            _EventCallBack = new NT_PB_SDKEventCallBack(PbSDKEventCallBack);
            _SetEventCallBackDictionary = new ConcurrentDictionary<IntPtr, PbSetEventCallBack>();
        }

        ~SmartPublisher()
        {
            RemoveHandle();
        }

        public List<string> AuidoInputDevicesList { get; set; }

        // 字节对齐
        int NT_ByteAlign(int d, int a)
        {
            return ((d) + (a - 1)) & ~(a - 1);
        }

        static bool _IsInit { get; set; }

        public static void InitSDK()
        {
            UInt32 isInited = NTSmartPublisherSDK.NT_PB_Init(0, IntPtr.Zero);
            if (isInited != 0) throw new Exception("初始化流媒体推送失败！");
            _IsInit = true;
        }

        public static void UnInitSDK()
        {
            NTSmartPublisherSDK.NT_PB_UnInit();
            _IsInit = false;
        }

        void InitLoad()
        {
            if (!_IsInit) throw new Exception("初始化流媒体推送失败，请退出启动程序！");

            _Config.VideoRateControl.AvgBitRate = CalBitRate((int)NT_DEF_VALUE.DEF_FRAME_RATE, _Config.VideoCollectionSetting.ScreenClip.Width, _Config.VideoCollectionSetting.ScreenClip.Height);

            _Config.VideoRateControl.VideoMaxBitRate = CalMaxKBitRate((int)NT_DEF_VALUE.DEF_FRAME_RATE, _Config.VideoCollectionSetting.ScreenClip.Width, _Config.VideoCollectionSetting.ScreenClip.Height);

            //关键帧=帧率*3
            _Config.VideoRateControl.KeyFrame = _Config.VideoCollectionSetting.CaptureFps * 3;

            int auido_devices = 0;

            if (NTBaseCodeDefine.NT_ERC_OK == NTSmartPublisherSDK.NT_PB_GetAuidoInputDeviceNumber(ref auido_devices))
            {
                if (auido_devices > 0)
                {
                    for (int i = 0; i < auido_devices; ++i)
                    {
                        StringBuilder name = new StringBuilder(512);

                        string w_name = string.Empty;

                        if (NTBaseCodeDefine.NT_ERC_OK == NTSmartPublisherSDK.NT_PB_GetAuidoInputDeviceName((uint)i, name, 512))
                        {
                            w_name = name.ToString();
                        }

                        string audio_name = string.Empty;

                        if (name.Length == 0)
                        {
                            audio_name = "音频采集设备-";
                        }
                        else
                        {
                            audio_name = w_name + "-";
                        }

                        audio_name = audio_name + (i + 1);

                        byte[] buffer1 = Encoding.Default.GetBytes(audio_name);
                        byte[] buffer2 = Encoding.Convert(Encoding.UTF8, Encoding.Default, buffer1, 0, buffer1.Length);
                        string strBuffer = Encoding.Default.GetString(buffer2, 0, buffer2.Length);

                        AuidoInputDevicesList.Add(strBuffer);
                    }

                    _Config.AudioCollectionSetting.AudioDevice = AuidoInputDevicesList.FirstOrDefault();
                }
            }

            _PublisherHandle = new IntPtr();

            _WindowHandle = GetForegroundWindow();
        }

        void PbEventCallBack(UInt32 event_id, Int64 param1, Int64 param2, UInt64 param3, UInt64 param4, [MarshalAs(UnmanagedType.LPStr)] String param5, [MarshalAs(UnmanagedType.LPStr)] String param6, IntPtr param7)
        {
            string statusMessage = string.Empty;
            string event_log = string.Empty;
            switch (event_id)
            {
                case (uint)NTSmartPublisherDefine.NT_PB_E_EVENT_ID.NT_PB_E_EVENT_ID_CONNECTING:
                    event_log = " push connecting";
                    if (!String.IsNullOrEmpty(param5))
                    {
                        statusMessage = event_log + " url:" + param5;
                    }
                    break;

                case (uint)NTSmartPublisherDefine.NT_PB_E_EVENT_ID.NT_PB_E_EVENT_ID_CONNECTION_FAILED:
                    event_log = " push connection failed";
                    if (!String.IsNullOrEmpty(param5))
                    {
                        statusMessage = event_log + " url:" + param5;
                    }
                    break;

                case (uint)NTSmartPublisherDefine.NT_PB_E_EVENT_ID.NT_PB_E_EVENT_ID_CONNECTED:
                    event_log = " push connected";
                    if (!String.IsNullOrEmpty(param5))
                    {
                        statusMessage = event_log + " url:" + param5;
                    }
                    break;

                case (uint)NTSmartPublisherDefine.NT_PB_E_EVENT_ID.NT_PB_E_EVENT_ID_DISCONNECTED:
                    event_log = " push disconnected";
                    if (!String.IsNullOrEmpty(param5))
                    {
                        statusMessage = event_log + " url:" + param5;
                    }
                    break;

                case (uint)NTSmartPublisherDefine.NT_PB_E_EVENT_ID.NT_PB_E_EVENT_ID_RECORDER_START_NEW_FILE:
                    event_log = " start new recorder file";
                    if (!String.IsNullOrEmpty(param5))
                    {
                        statusMessage = event_log + " file name:" + param5;
                    }
                    break;

                case (uint)NTSmartPublisherDefine.NT_PB_E_EVENT_ID.NT_PB_E_EVENT_ID_ONE_RECORDER_FILE_FINISHED:
                    event_log = " finish recorder file";
                    if (!String.IsNullOrEmpty(param5))
                    {
                        statusMessage = event_log + " file name:" + param5;
                    }
                    break;

                default:
                    break;
            }

#if DEBUG
            Console.WriteLine(statusMessage);
#else
            if (_Config.IsLog) _StatusMessageList.Enqueue(statusMessage);
#endif
           
        }

        ConcurrentQueue<string> _StatusMessageList { get; set; }

        static void PbSDKEventCallBack(IntPtr handle, IntPtr user_data, UInt32 event_id, Int64 param1, Int64 param2, UInt64 param3, UInt64 param4, [MarshalAs(UnmanagedType.LPStr)] String param5, [MarshalAs(UnmanagedType.LPStr)] String param6, IntPtr param7)
        {
            if (_SetEventCallBackDictionary.ContainsKey(handle)) _SetEventCallBackDictionary[handle](event_id, param1, param2, param3, param4, param5, param6, param7);
        }

        private bool OpenPublisherHandle()
        {
            if (_PublisherHandle != IntPtr.Zero) return true;

            // 视频
            UInt32 video_option = (UInt32)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_NO_VIDEO;

            if (_Config.VideoCollectionSetting.IsDesktopInput)
            {
                //屏幕模式
                video_option = (UInt32)NTSmartPublisherDefine.NT_PB_E_VIDEO_OPTION.NT_PB_E_VIDEO_OPTION_SCREEN;
            }

            // 音频
            UInt32 audio_option = (UInt32)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_NO_AUDIO;

            if (_Config.AudioCollectionSetting.IsAuidoMicInput && _Config.AudioCollectionSetting.IsAuidoSpeakerInput)
            {
                audio_option = (UInt32)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_CAPTURE_MIC_SPEAKER_MIXER;
            }
            else if (_Config.AudioCollectionSetting.IsAuidoMicInput)
            {
                //麦克风模式
                audio_option = (UInt32)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_CAPTURE_MIC;
            }
            else if (_Config.AudioCollectionSetting.IsAuidoSpeakerInput)
            {
                //扬声器模式
                audio_option = (UInt32)NTSmartPublisherDefine.NT_PB_E_AUDIO_OPTION.NT_PB_E_AUDIO_OPTION_CAPTURE_SPEAKER;
            }

            IntPtr publisherHandle = IntPtr.Zero;
            if (NTBaseCodeDefine.NT_ERC_OK != NTSmartPublisherSDK.NT_PB_Open(out publisherHandle, video_option, audio_option, 0, IntPtr.Zero))
            {
                throw new Exception("Call open failed!");
            }

            _PublisherHandle = publisherHandle;

            if (_PublisherHandle != IntPtr.Zero)
            {
                _SetEventCallBackDictionary.AddOrUpdate(_PublisherHandle, new PbSetEventCallBack(PbEventCallBack), (key, value) => value);

                NTSmartPublisherSDK.NT_PB_SetEventCallBack(_PublisherHandle, _WindowHandle, _EventCallBack);
                return true;
            }
            else return false;
        }

        int CalBitRate(int frame_rate, int w, int h)
        {
            int kbit_rate = 2000;

            int area = w * h;

            if (area <= (320 * 300))
            {
                kbit_rate = 480;
            }
            else if (area <= (360 * 320))
            {
                kbit_rate = 560;
            }
            else if (area <= (800 * 600))
            {
                kbit_rate = 920;
            }
            else if (area <= (900 * 700))
            {
                kbit_rate = 2000;
            }
            else if (area <= (1280 * 720))
            {
                kbit_rate = 4000;
            }
            else if (area <= 1366 * 768)
            {
                kbit_rate = 6000;
            }
            else
            {
                kbit_rate = 6500;
            }

            kbit_rate = kbit_rate * frame_rate / 25;

            if (kbit_rate < 80)
                kbit_rate = 80;

            return kbit_rate;
        }

        int CalMaxKBitRate(int frame_rate, int w, int h)
        {
            int max_kbit_rate = 2000;

            int area = w * h;

            if (area <= (320 * 300))
            {
                max_kbit_rate = 1000;
            }
            else if (area <= (360 * 320))
            {
                max_kbit_rate = 1200;
            }
            else if (area <= (800 * 600))
            {
                max_kbit_rate = 2000;
            }
            else if (area <= (900 * 700))
            {
                max_kbit_rate = 3000;
            }
            else if (area <= (1280 * 720))
            {
                max_kbit_rate = 6000;
            }
            else if (area <= 1366 * 768)
            {
                max_kbit_rate = 8000;
            }
            else
            {
                max_kbit_rate = 8000;
            }

            max_kbit_rate = max_kbit_rate * frame_rate / 25;

            if (max_kbit_rate < 130)
                max_kbit_rate = 140;

            return max_kbit_rate;
        }

        public void StartPublish(string publishUrl)
        {
            if (!string.IsNullOrEmpty(publishUrl)) _Config.RtmpPublisher.PublishUrl = publishUrl;

            if (_PublisherHandle == IntPtr.Zero)
            {
                if (!OpenPublisherHandle()) return;

                SetCommonOptionToPublisherSDK();
            }

            if (String.IsNullOrEmpty(_Config.RtmpPublisher.PublishUrl))
            {
                NTSmartPublisherSDK.NT_PB_Close(_PublisherHandle);
                _PublisherHandle = IntPtr.Zero;

                throw new Exception("请输入推送地址");
            }

            if (_PublisherHandle == IntPtr.Zero)
            {
                throw new Exception("[publish] handle with null");
            }

            NTSmartPublisherSDK.NT_PB_SetURL(_PublisherHandle, _Config.RtmpPublisher.PublishUrl, IntPtr.Zero);

            if (NTBaseCodeDefine.NT_ERC_OK != NTSmartPublisherSDK.NT_PB_StartPublisher(_PublisherHandle, IntPtr.Zero))
            {
                NTSmartPublisherSDK.NT_PB_Close(_PublisherHandle);

                _PublisherHandle = IntPtr.Zero;

                _IsPublishing = false;

                throw new Exception("调用推流接口失败");
            }

            _IsPublishing = true;
        }

        public void StopPublish()
        {
            if (_IsPublishing)
            {
                NTSmartPublisherSDK.NT_PB_StopPublisher(_PublisherHandle);
                _IsPublishing = false;
            }

            if (!_IsPublishing && !_IsRecording)
            {
                NTSmartPublisherSDK.NT_PB_Close(_PublisherHandle);
                _PublisherHandle = IntPtr.Zero;

                WriteStatusLog();
            }
        }

        void WriteStatusLog()
        {
            if (!_Config.IsLog) return;

            Utility.LoggerProxy.Info("SmartPublisher", "WriteStatusLog",  _StatusMessageList.ToList());

            _StatusMessageList = new ConcurrentQueue<string>();
        }

        public void StartRecord(string dir, string fileName)
        {
            _Config.LocalRecorder.RecordDir = dir;
            _Config.LocalRecorder.FileNamePrefix = fileName;

            if (_PublisherHandle == IntPtr.Zero)
            {
                if (!OpenPublisherHandle()) return;
 
                SetCommonOptionToPublisherSDK();
            }

            byte[] buffer1 = Encoding.Default.GetBytes(_Config.LocalRecorder.RecordDir);
            byte[] buffer2 = Encoding.Convert(Encoding.UTF8, Encoding.Default, buffer1, 0, buffer1.Length);
            string recDir = Encoding.Default.GetString(buffer2, 0, buffer2.Length);

            if (string.IsNullOrEmpty(recDir)) throw new Exception("请设置录像目录");

            if (_PublisherHandle == IntPtr.Zero) return;

            uint ret = NTSmartPublisherSDK.NT_PB_SetRecorderDirectory(_PublisherHandle, recDir, IntPtr.Zero);

            if (NTBaseCodeDefine.NT_ERC_OK != ret)
            {
                NTSmartPublisherSDK.NT_PB_Close(_PublisherHandle);
                _PublisherHandle = IntPtr.Zero;

                throw new Exception("设置录像目录失败，请确保目录存在且是英文目录");
            }

            uint rec_max_file_size = (uint)_Config.LocalRecorder.SingleRecordMaxFileSize;
            rec_max_file_size *= 1024;
            NTSmartPublisherSDK.NT_PB_SetRecorderFileMaxSize(_PublisherHandle, rec_max_file_size);

            NT_PB_RecorderFileNameRuler rec_name_ruler = new NT_PB_RecorderFileNameRuler();

            string rec_file_name_prefix_ = _Config.LocalRecorder.FileNamePrefix;
            rec_name_ruler.file_name_prefix_ = rec_file_name_prefix_.ToString();

            rec_name_ruler.append_date_ = _Config.LocalRecorder.IsAppendDate ? 1 : 0;

            rec_name_ruler.append_time_ = _Config.LocalRecorder.IsAppendTime ? 1 : 0;

            NTSmartPublisherSDK.NT_PB_SetRecorderFileNameRuler(_PublisherHandle, ref rec_name_ruler);

            if (NTBaseCodeDefine.NT_ERC_OK != NTSmartPublisherSDK.NT_PB_StartRecorder(_PublisherHandle, IntPtr.Zero))
            {
                NTSmartPublisherSDK.NT_PB_Close(_PublisherHandle);
                _PublisherHandle = IntPtr.Zero;
                _IsRecording = false;

                throw new Exception("调用录像接口失败!");
            }

            _IsRecording = true;
        }

        public void StopRecord()
        {
            if (_IsRecording)
            {
                NTSmartPublisherSDK.NT_PB_StopRecorder(_PublisherHandle);
                _IsRecording = false;
            }

            if (!_IsPublishing && !_IsRecording)
            {
                NTSmartPublisherSDK.NT_PB_Close(_PublisherHandle);
                RemoveHandle();
                _PublisherHandle = IntPtr.Zero;

                WriteStatusLog();
            }

            Thread.Sleep(1000);
        }

        void RemoveHandle()
        {
            try
            {
                PbSetEventCallBack setEventCallBack = null;
                if (_PublisherHandle != IntPtr.Zero && _SetEventCallBackDictionary.ContainsKey(_PublisherHandle)) _SetEventCallBackDictionary.TryRemove(_PublisherHandle, out setEventCallBack);
            }
            catch
            {

            }
        }

        void SetCommonOptionToPublisherSDK()
        {
            if (_PublisherHandle == IntPtr.Zero)
            {
                throw new Exception("SetCommonOptionToPublisherSDK, publisher_handle_ with null..");
            }

            // 视频相关设置
            if (_Config.VideoCollectionSetting.IsDesktopInput)
            {

                uint left = (uint)_Config.VideoCollectionSetting.ScreenClip.Left;
                uint top = (uint)_Config.VideoCollectionSetting.ScreenClip.Top;
                uint width = (uint)_Config.VideoCollectionSetting.ScreenClip.Width;
                uint height = (uint)_Config.VideoCollectionSetting.ScreenClip.Height;

                //桌面
                NTSmartPublisherSDK.NT_PB_SetScreenClip(_PublisherHandle, left, top, width, height);

                NTSmartPublisherSDK.NT_PB_SetFrameRate(_PublisherHandle, (uint)_Config.VideoCollectionSetting.CaptureFps);


                NTSmartPublisherSDK.NT_PB_SetVideoBitRate(_PublisherHandle, _Config.VideoRateControl.AvgBitRate);

                NTSmartPublisherSDK.NT_PB_SetVideoQuality(_PublisherHandle, _Config.VideoRateControl.VideoQuality);

                NTSmartPublisherSDK.NT_PB_SetVideoMaxBitRate(_PublisherHandle, _Config.VideoRateControl.VideoMaxBitRate);

                NTSmartPublisherSDK.NT_PB_SetVideoKeyFrameInterval(_PublisherHandle, _Config.VideoRateControl.KeyFrame);

                int profile_sel = (int)_Config.VideoRateControl.H264Profile;

                if (profile_sel != -1)
                {
                    NTSmartPublisherSDK.NT_PB_SetVideoEncoderProfile(_PublisherHandle, profile_sel + 1);
                }

                NTSmartPublisherSDK.NT_PB_SetVideoEncoderSpeed(_PublisherHandle, _Config.VideoRateControl.VideoEncodeSpeed);

                // 音频相关设置
                if (_Config.AudioCollectionSetting.IsAuidoMicInput && !string.IsNullOrEmpty(_Config.AudioCollectionSetting.AudioDevice))
                {
                    if (AuidoInputDevicesList.Count > 0)
                    {
                        NTSmartPublisherSDK.NT_PB_SetAuidoInputDeviceId(_PublisherHandle, (uint)0);
                    }
                }

                if (_Config.AudioCollectionSetting.IsSpeexEncoder)
                {
                    NTSmartPublisherSDK.NT_PB_SetPublisherAudioCodecType(_PublisherHandle, 2);

                    NTSmartPublisherSDK.NT_PB_SetPublisherSpeexEncoderQuality(_PublisherHandle, _Config.AudioCollectionSetting.SpeexQuality);
                }
                else
                {
                    NTSmartPublisherSDK.NT_PB_SetPublisherAudioCodecType(_PublisherHandle, 1);
                }

                if (_Config.AudioCollectionSetting.IsAuidoMicInput || _Config.AudioCollectionSetting.IsAuidoSpeakerInput)
                {
                    if (_Config.AudioCollectionSetting.IsMute)
                    {
                        NTSmartPublisherSDK.NT_PB_SetMute(_PublisherHandle, 1);
                    }
                }
            }
        }
    }

    public enum NT_DEF_VALUE : uint
    {
        DEF_FRAME_RATE = 5,
        DEF_KEY_FRAME = 10,
        DEF_VIDEO_QUALITY = 15
    }
}
